#include "tool.h"
#include <myhead.h>

// 超时时间
struct timeval outtime = {3, 0};
// 信号灯
sem_t sem_for_list;
// 头结点
list_t *global_head = NULL;
// 消息队列key和id // 注: 可以搞成双通道( 即2个消息队列进行通信 )
key_t key = 0;
int msgid;
// 信号量key和id
int sem_key;
int semid;

void create_daemon(void)
{
    pid_t pid;
    pid = fork(); // 创建新进程
    if (pid < 0)
    {
        perror("fork error");
        return;
    }
    if (pid > 0) // 父进程退出
    {
        exit(0);
    }
    if (setsid() < 0) // 让子进程新建会话组 并且让自己成为会话组组长
    {                 // 摆脱原会话的控制 摆脱原终端 创建新终端(隐形的 并在那个新终端运行 也就是会话与此终端绑定)
        perror("setsid error");
        return;
    }
    for (int i = 0; i < 1024; i++)
    {
        close(i); // 关闭所有文件描述符
    }

    // 一般确实不建议在当前路径下 因为会影响工作目录的卸载 但是介于其他文件都在这个目录下 测试阶段就不切换了
    chdir("./");
    umask(0);    // 重设文件掩码 使文件权限最大化 更容易控制
    close(0);
    int fd = open("/dev/null", O_RDWR); // 空设备文件 demo用 写入这里的数据会被丢弃
    if (fd < 0)
    {
        perror("open error");
        return;
    }
    // dup2(fd, 0); // 重定向标准输入
    dup2(fd, 1); // 重定向标准输出
    dup2(fd, 2); // 重定向标准错误
    return;
}

int mysemlamp_init(void)
{
    sem_key = ftok(MYPATH, 'Z');
    if (-1 == sem_key)
    {
        perror("ftok error");
        return -1;
    }
    semid = semget(sem_key, 1, IPC_CREAT | 0666);
    if (-1 == semid)
    {
        perror("semget error");
        return -1;
    }
    semctl(semid, 0, SETVAL, 1);
    return 0;
}

int net_init(const char *Tport)
{
    if (NULL == Tport)
    {
        printf("Tport is NULL\n");
        return -1;
    }
    int port = atoi(Tport);
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (-1 == sockfd)
    {
        perror("socket error");
        return -1;
    }

    struct sockaddr_in service_addr;
    service_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    service_addr.sin_port = htons(port);
    service_addr.sin_family = AF_INET;

    if (bind(sockfd, (struct sockaddr *)&service_addr, sizeof(service_addr)))
    {
        perror("bind error");
        return -1;
    }
    if (listen(sockfd, 5))
    {
        perror("listen error");
        return -1;
    }

    return sockfd;
}

int list_create(list_t **head)
{
    if (NULL == head)
    {
        return -1;
    }
    *head = (list_t *)malloc(sizeof(list_t));
    if (NULL == *head)
    {
        return -1;
    }
    (*head)->fd = -1;
    (*head)->tid = -1;
    (*head)->next = NULL;
    global_head = *head;
    return 0;
}

int msg_queue_init(void)
{
    key = ftok(MYPATH, 'A'); // 生成key
    if (-1 == key)
    {
        perror("ftok error");
        return -1;
    }
    msgid = msgget(key, IPC_CREAT | 0666); // 创建消息队列
    if (-1 == msgid)
    {
        perror("msgget error");
        return -1;
    }

    return msgid;
}

int list_insert(list_t *head, int fd, const char *id, pthread_t tid)
{
    if (NULL == head)
    {
        return -1;
    }
    list_t *new = (list_t *)malloc(sizeof(list_t));
    if (NULL == new)
    {
        return -1;
    }
    new->fd = fd;
    new->tid = tid;
    new->next = head->next;
    head->next = new;
    strcpy(new->id, id);
    return 0;
}

int list_find_by_id(list_t *head, const char *id)
{
    if (NULL == head)
    {
        return -1;
    }
    list_t *p = head->next;
    while (p)
    {
        if (!strcmp(p->id, id))
        {
            return 0;
        }
        p = p->next;
    }

    return -1;
}

int list_delete(list_t *head, int fd)
{
    if (NULL == head)
    {
        return -1;
    }
    list_t *p = head; // p是q的前驱
    list_t *q = head->next;
    while (q)
    {
        if (q->fd == fd)
        {
            p->next = q->next;
            free(q);
            q = NULL;
            return 0;
        }
        p = q;
        q = q->next;
    }

    return -1;
}

int list_traverse(list_t *head)
{
    if (NULL == head)
    {
        return -1;
    }
    list_t *p = head->next;
    myprotocol_t send_info = {0};
    myprotocol_t recv_info = {0};
    while (p)
    {
        memset(&send_info, 0, sizeof(myprotocol_t));
        memset(&recv_info, 0, sizeof(myprotocol_t));
        send_info.cmd = ISONLINE_REQ;
        send(p->fd, &send_info, sizeof(myprotocol_t), 0);
        outtime.tv_sec = 3; // 容忍的网络延迟时间
        outtime.tv_usec = 0;
        // 设置接收超时 和 发送超时( 限制网络请求在尝试发送数据时等待网络可用的最大时间 )
        if (setsockopt(p->fd, SOL_SOCKET, SO_RCVTIMEO, &outtime, sizeof(outtime)))
        {
            perror("setsockopt error");
            continue; // 继续下一个 而不是退出
        }
        // 用户离线
        if (0 >= recv(p->fd, &recv_info, sizeof(myprotocol_t), 0))
        {
            // 重置fd的设置
            outtime.tv_sec = 0;
            outtime.tv_usec = 0;
            if (setsockopt(p->fd, SOL_SOCKET, SO_RCVTIMEO, &outtime, sizeof(outtime)))
            {
                perror("setsockopt error");
                continue;
            }
            printf("fd 为 %d 的用户已经离线\n", p->fd);
            pthread_cancel(p->tid);  // 注: 需要有取消点
            sem_wait(&sem_for_list); // 临界区 防止多线程同时操作链表
            close(p->fd);
            list_delete(head, p->fd);
            sem_post(&sem_for_list);
            p = head;
        }
        else
        {
            // 重置fd的设置
            outtime.tv_sec = 0;
            outtime.tv_usec = 0;
            if (setsockopt(p->fd, SOL_SOCKET, SO_RCVTIMEO, &outtime, sizeof(outtime)))
            {
                perror("setsockopt error");
                continue;
            }
            if (ISONLINE_ACK == recv_info.cmd)
            {
                printf("fd 为 %d 的用户还在线\n", p->fd);
            }
            else
            {
                printf("fd 为 %d 的用户 有反馈 但是收到的不是ACK\n", p->fd);
                pthread_cancel(p->tid);
                sem_wait(&sem_for_list);
                close(p->fd); // 也可以放在list_delete中
                list_delete(head, p->fd);
                sem_post(&sem_for_list);
                p = head;
            }
        }
        p = p->next;
    }

    return 0;
}

int list_free(list_t **head)
{
    if (NULL == head)
    {
        return -1;
    }
    list_t *p = (*head)->next;
    list_t *q = NULL;
    while (p)
    {
        q = p->next;
        free(p);
        p = q;
    }
    free(*head);
    *head = NULL;

    return 0;
}

void *handle_thread(void *arg)
{
    if (NULL == arg)
    {
        return NULL;
    }
    int fd = (int)(long)arg; // 目前运行在x86-64的ubuntu_64位平台上,故可以如此
    // 我通过值拷贝直接用了,所以不用加锁,但是前提条件是必须是指针大小或者比指针大小要小才行
    FILE *fp = NULL;
    char file_buf[32] = {0};
    char id[20] = {0};
    char pw[20] = {0};
    myprotocol_t recv_buf = {0};
    int i = 0;

    if (recv(fd, &recv_buf, sizeof(myprotocol_t), 0) <= 0)
    {
        close(fd);
        return NULL;
    }
    printf("用户 %s 请求登录\n", recv_buf.user_info.id);

    fp = fopen("user.txt", "r"); // demo 可以使用数据库实现
    if (NULL == fp)
    {
        perror("fopen error");
        close(fd);
        return NULL;
    }

    if (fgets(file_buf, sizeof(file_buf), fp) != NULL)
    {
        // 提取用户名，假定格式为 id{username}
        sscanf(file_buf, "id{%[^}]}", id);
    }
    memset(file_buf, 0, sizeof(file_buf));
    // 读取密码行
    if (fgets(file_buf, sizeof(file_buf), fp) != NULL)
    {
        // 提取密码，假定格式为 pw{password}
        sscanf(file_buf, "pw{%[^}]}", pw); // %[^}]是一个扫描集,它告诉sscanf读取直到遇到}为止的所有字符
    }
    // printf("id: %s pw: %s\n", id, pw);
    if (!strcmp(recv_buf.user_info.id, id) && !strcmp(recv_buf.user_info.pw, pw))
    {
        recv_buf.user_info.flag = 1;
    }
    else
    {
        recv_buf.user_info.flag = 0;
    }
    fclose(fp);
    if (recv_buf.user_info.flag == 1)
    {
        // 登录成功
        printf("用户 %s 登录成功\n", recv_buf.user_info.id);
        send(fd, &recv_buf, sizeof(myprotocol_t), 0);
        pthread_t tid = pthread_self();
        sem_wait(&sem_for_list);
        list_insert((list_t *)global_head, fd, recv_buf.user_info.id, tid);
        sem_post(&sem_for_list);
        // 注: 连接的时候 判断的id也是下位机的id 所以要操控的也是这个下位机 他的id和下位机id一样
        long msg_recv_type = 0;
        char* temp = recv_buf.user_info.id; 
        while (*temp)
        {
            msg_recv_type += *temp;
            temp++;
        }
        while (1)
        {
            memset(&recv_buf, 0, sizeof(myprotocol_t));
            /*等待消息队列中的数据*/
            if (-1 == msgrcv(msgid, &recv_buf, sizeof(myprotocol_t) - sizeof(recv_buf.msgtype), msg_recv_type, 0))
            {
                perror("msgrcv error 1");
                continue;
            }
            recv_buf.msgtype = recv_buf.uid * 2;
            /*将消息数据 通过 socket 发送给智能硬件*/
            send(fd, &recv_buf, sizeof(myprotocol_t), 0);
            /*等待智能硬件的执行结果*/
            int ret = recv(fd, &recv_buf, sizeof(myprotocol_t), 0);
            if (0 >= ret)
            {
                printf("用户 %s 断开连接\n", recv_buf.user_info.id);
                sem_wait(&sem_for_list);
                list_delete(global_head, fd);
                sem_post(&sem_for_list);
                close(fd);
                break;
            }
            // printf("温度和湿度为: %f %f\n", recv_buf.env_info.tem, recv_buf.env_info.hum); // demo
            /*将智能硬件返回的数据结果发送到消息队列*/
            msgsnd(msgid, &recv_buf, sizeof(myprotocol_t) - sizeof(recv_buf.msgtype), 0);

            // printf("用户 %s 发送了命令 %d\n", recv_buf.user_info.id, recv_buf.cmd); // demo
            // sleep(1); // top -Hp 进程号 能看到该进程的线程的CPU占用率 (取消点 用于测试)
        }
    }
    else
    {
        // 登录失败
        printf("用户 %s 登录失败\n", recv_buf.user_info.id);
        send(fd, &recv_buf, sizeof(myprotocol_t), 0);
        close(fd);
        return NULL;
    }

    return NULL;
}

void *heartbeat_thread(void *arg)
{
    if (NULL == arg)
    {
        return NULL;
    }
    list_t *head = (list_t *)arg;
    while (1)
    {
        printf("10秒后开始检测心跳\n");
        sleep(10);
        list_traverse(head);
    }
    return NULL;
}

void *monitor_hw_thread(void *arg)
{
    myprotocol_t buf = {0};
    while (1)
    {
        memset(&buf, 0, sizeof(myprotocol_t));
        // size是消息的大小,不包括消息类型
        if (-1 == msgrcv(msgid, &buf, sizeof(myprotocol_t) - sizeof(buf.msgtype), BOA_TO_SER_ISONLINE, 0))
        {
            perror("msgrcv error 2");
            continue;
        }

        sem_wait(&sem_for_list);
        if (list_find_by_id(global_head, buf.user_info.id))
        {
            // 不在线
            buf.user_info.flag = 0;
        }
        else
        {
            // 在线
            buf.user_info.flag = 1;
        }
        sem_post(&sem_for_list);
        buf.msgtype = buf.uid * 2;
        if (-1 == msgsnd(msgid, &buf, sizeof(myprotocol_t) - sizeof(buf.msgtype), 0))
        {
            perror("msgsnd error 2");
            continue;
        }
    }

    return NULL;
}